package encriptacion.Decoder;

import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
import sun.misc.CEStreamExhausted;

public abstract class CharacterDecoder
{
abstract protected int bytesPerAtom();
abstract protected int bytesPerLine();
protected void decodeBufferPrefix(PushbackInputStream aStream, OutputStream bStream) throws IOException { }
protected void decodeBufferSuffix(PushbackInputStream aStream, OutputStream bStream) throws IOException { }
protected int decodeLinePrefix(PushbackInputStream aStream, OutputStream bStream) throws IOException {
             return (bytesPerLine());
         }
protected void decodeLineSuffix(PushbackInputStream aStream, OutputStream bStream) throws IOException { }
//protected void decodeAtom(PushbackInputStream aStream, OutputStream bStream, int l) throws IOException
//{
//             throw new CEStreamExhausted();
//}

protected int readFully(InputStream in, byte buffer[], int offset, int len)
             throws java.io.IOException {
             for (int i = 0; i < len; i++) {
                 int q = in.read();
                 if (q == -1)
                     return ((i == 0) ? -1 : i);
                 buffer[i+offset] = (byte)q;
             }
             return len;
         }


 protected void decodeAtom(PushbackInputStream inStream, OutputStream outStream, int rem)
            throws java.io.IOException, Exception
         {
             int     i;
             byte    a = -1, b = -1, c = -1, d = -1;

             if (rem < 2) {
                 throw new Exception("BASE64Decoder: Not enough bytes for an atom.");
            }
             do {
                 i = inStream.read();
                 if (i == -1) {
                     throw new Exception();
                 }
             } while (i == '\n' || i == '\r');
             decode_buffer[0] = (byte) i;

             i = readFully(inStream, decode_buffer, 1, rem-1);
             if (i == -1) {
                 throw new Exception();
             }

             if (rem > 3 && decode_buffer[3] == '=') {
                 rem = 3;
             }
             if (rem > 2 && decode_buffer[2] == '=') {
                 rem = 2;
             }
             switch (rem) {
             case 4:
                 d = pem_convert_array[decode_buffer[3] & 0xff];
               // NOBREAK
             case 3:
                 c = pem_convert_array[decode_buffer[2] & 0xff];
                 // NOBREAK
             case 2:
                 b = pem_convert_array[decode_buffer[1] & 0xff];
                 a = pem_convert_array[decode_buffer[0] & 0xff];
                 break;
             }

             switch (rem) {
             case 2:
                 outStream.write( (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3)) );
                break;
             case 3:
                 outStream.write( (byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3)) );
                 outStream.write( (byte) (((b << 4) & 0xf0) | ((c >>> 2) & 0xf)) );
                 break;
             case 4:
                 outStream.write( (byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3)) );
                 outStream.write( (byte) (((b << 4) & 0xf0) | ((c >>> 2) & 0xf)) );
                 outStream.write( (byte) (((c << 6) & 0xc0) | (d  & 0x3f)) );
                 break;
             }
             return;
         }


   private final static char pem_array[] =
   {
     //       0   1   2   3   4   5   6   7
             'A','B','C','D','E','F','G','H', // 0
             'I','J','K','L','M','N','O','P', // 1
             'Q','R','S','T','U','V','W','X', // 2
             'Y','Z','a','b','c','d','e','f', // 3
             'g','h','i','j','k','l','m','n', // 4
             'o','p','q','r','s','t','u','v', // 5
             'w','x','y','z','0','1','2','3', // 6
             '4','5','6','7','8','9','+','/'  // 7
   };

private final static byte pem_convert_array[] = new byte[256];

         static {
              for (int i = 0; i < 255; i++) {
                  pem_convert_array[i] = -1;
              }
             for (int i = 0; i < pem_array.length; i++) {
                  pem_convert_array[pem_array[i]] = (byte) i;
              }
          }

         byte decode_buffer[] = new byte[4];


public void decodeBuffer(InputStream aStream, OutputStream bStream) throws IOException, Exception {
             int     i;
             int     totalBytes = 0;
  
             PushbackInputStream ps = new PushbackInputStream (aStream);
             decodeBufferPrefix(ps, bStream);
             while (true) {
                 int length;
  
                 try {
                     length = decodeLinePrefix(ps, bStream);
                     for (i = 0; (i+bytesPerAtom()) < length; i += bytesPerAtom()) {
                         decodeAtom(ps, bStream, bytesPerAtom());
                         totalBytes += bytesPerAtom();
                     }
                     if ((i + bytesPerAtom()) == length) {
                         decodeAtom(ps, bStream, bytesPerAtom());
                         totalBytes += bytesPerAtom();
                     } else {
                         decodeAtom(ps, bStream, length - i);
                         totalBytes += (length - i);
                     }
                     decodeLineSuffix(ps, bStream);
                 } catch (CEStreamExhausted e) {
                     break;
                }
             }
             decodeBufferSuffix(ps, bStream);
         }

public byte decodeBuffer(String inputString)[] throws IOException, Exception {
             byte    inputBuffer[] = new byte[inputString.length()];
             ByteArrayInputStream inStream;
             ByteArrayOutputStream outStream;
  
             inputString.getBytes(0, inputString.length(), inputBuffer, 0);
             inStream = new ByteArrayInputStream(inputBuffer);
             outStream = new ByteArrayOutputStream();
             decodeBuffer(inStream, outStream);
             return (outStream.toByteArray());
         }

}
